/*
 * main.c
 *
 *  Created on: Apr 12, 2011
 *      Author: Grupo 8
 */

#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <openssl/evp.h>
#include <arpa/inet.h>
#include "include/params.h"
#include "include/wav.h"
#include "include/steganography.h"

int embedProcess(Params params, wavT * carrier, const EVP_CIPHER *cypher);
int CipherData(const EVP_CIPHER * cipher, Params params, unsigned char * in, int inl, unsigned char * out);

int
main(int argc, char * argv[])
{
	Params params;
	const EVP_CIPHER *cypher;
	BYTE * extraction, * out;
	char * extension;
	int extensionSize, offset, size;

	initParams(&params);
	//PARA PROBAR HAY 2 MANERAS, UNA ES SETEAR EL ARGV, LA OTRA EN EL RUNN CONF HAY UNA OPCION, USEN ESO.
	if (getParams(argc, argv, &params) == 0)
	{
		return 1;
	}



	switch(params.a){
		case AES128:
			switch(params.m){
				case ECB:
					cypher = EVP_aes_128_ecb();
					break;
				case CBC:
					cypher=EVP_aes_128_cbc();
					break;
				case OFB:
					cypher=EVP_aes_128_ofb();
					break;
				case CFB:
					cypher=EVP_aes_128_cfb8();
					break;
				default:
					cypher=EVP_aes_128_cbc();
			}
			break;
		case AES192:
			switch(params.m){
				case ECB:
					cypher=EVP_aes_192_ecb();
					break;
				case CBC:
					cypher=EVP_aes_192_cbc();
					break;
				case OFB:
					cypher=EVP_aes_192_ofb();
					break;
				case CFB:
					cypher=EVP_aes_192_cfb8();
					break;
				default:
					cypher=EVP_aes_192_cbc();
			}
			break;
		case AES256:
			switch(params.m){
				case ECB:
					cypher=EVP_aes_256_ecb();
					break;
				case CBC:
					cypher=EVP_aes_256_cbc();
					break;
				case OFB:
					cypher=EVP_aes_256_ofb();
					break;
				case CFB:
					cypher=EVP_aes_256_cfb8();
					break;
				default:
					cypher=EVP_aes_256_cbc();
			}
			break;
		case DES:
			switch(params.m)
			{
				case ECB:
					cypher=EVP_des_ecb();
					break;
				case CBC:
					cypher=EVP_des_cbc();
					break;
				case OFB:
					cypher=EVP_des_ofb();
					break;
				case CFB:
					cypher=EVP_des_cfb8();
					break;
				default:
					cypher=EVP_des_cbc();
			}
			break;
		default:
			cypher=EVP_aes_128_cbc();
	}

	wavT * wav = loadWavFile(params.p);

	if (wav == NULL)
	{
		fprintf(stderr, "Error, wav file invalido o inexistente\n");
		return 1;
	}

	if (params.sop == EMBED)
	{
		if (!embedProcess(params, wav, cypher))
		{
			free(wav -> data.soundData);
			free(wav);
			return 1;
		}
		saveWavFile(params.out, wav);
	}
	else
	{
		offset = wav -> fmt.wBitsPerSample / CHAR_BIT;
		extraction = getStegoFileSize(params, wav, offset, &size);
		StegoFileT * sfile = calloc(1, sizeof(StegoFileT));

		if ( (extraction = extractFile(params, extraction, offset, size)) == NULL)
		{
			free(wav -> data.soundData);
			free(wav);
			return 1;
		}

		if (params.pass[0] != '\0')
		{
			// size = total size of encryption chunk
			out = calloc(1, size + (params.a == DES? DES_BLOCK_SIZE : AES_BLOCK_SIZE));

			// decrypts chunk
			CipherData(cypher, params, extraction, size, out);

			// gets real size
			memcpy(&size, out, 4);
			size = ntohl(size);

			extensionSize = strlen((char *)out + size + sizeof(int));
			extension = calloc(1, extensionSize+1);
			strncpy(extension, (char *)out+size+sizeof(int), extensionSize);
			free(extraction);
			extraction = out + sizeof(int);
			sfile -> fileSize = size;
			sfile -> fileContents = calloc(1, size);
			memcpy(sfile -> fileContents, extraction, sfile -> fileSize);
			free(out);
		}
		else
		{
			sfile -> fileSize = size;
			sfile -> fileContents = calloc(1, size);
			memcpy(sfile -> fileContents, extraction, sfile -> fileSize);
			extension = getStegoFileExtension(params, wav, offset, size);
			free(extraction);
		}

		sfile -> extension = extension;
		writeStegoFile(params.out, sfile);
		printf("wav %s successfuly decoded into file %s\n", params.p, strcat(params.out,extension));
		freeStegoFile(sfile);
	}


	free(wav -> data.soundData);
	free(wav);
	return 0;
}

int
embedProcess(Params params, wavT * carrier, const EVP_CIPHER *cypher)
{
	StegoFileT * sfile;
	unsigned char * in, * out;
	int outl, inl, padsize;

	// Reads in the file to be hidden

	if ( (sfile = readStegoFile(params.in)) == NULL)
	{
		fprintf(stderr, "Error reading file to be hidden");
		return 1;
	}

	// calculate size of file size indicator + file contents + file extension length + \0
	inl = sizeof(DWORD) + sfile -> fileSize + strlen(sfile -> extension) + 1;

	// allocs space to hold fsize indicator, file contents, fextension and \0

	if ( (in = calloc(1, inl)) == NULL )
	{
		fprintf(stderr, "Error, out of memory when attempting to cipher file %s\n", params.in);
		freeStegoFile(sfile);
	}

	int endianSize = ntohl(sfile -> fileSize);

	// transfer into one chunk of data the file size indicator, file contents and file extension
	memcpy(in, &endianSize, 4);
	memcpy(in + sizeof(DWORD), sfile -> fileContents, sfile -> fileSize);
	memcpy(in + sizeof(DWORD) + sfile -> fileSize, sfile -> extension, strlen(sfile -> extension));


	// stenography + encrypting
	if (params.pass[0] != '\0')
	{
		// calculates extra space needed if padding occurs either in AES or DES cipher types
		if (params.a != DES)
		{
			padsize = (inl/AES_BLOCK_SIZE)*AES_BLOCK_SIZE + AES_BLOCK_SIZE;
		}
		else
		{
			padsize = (inl/DES_BLOCK_SIZE)*DES_BLOCK_SIZE + DES_BLOCK_SIZE;
		}

		// allocs space to hold data encrypted and its size before stenography occures
		if ( (out = calloc(1, sizeof(int) + padsize)) == NULL )
		{
			fprintf(stderr, "Error, out of memory when attempting to cipher file %s\n", params.in);
			free(in);
			freeStegoFile(sfile);
		}

		// encryption... moves out by 4 bytes to leave space for encryption total size
		outl = CipherData(cypher, params, in, inl, out+sizeof(int));

		// adds encryption total size to out
		int endianSize = ntohl(outl);
		outl += sizeof(int); // stenograph outl bytes + 4 of size indicator!!!
		memcpy(out, &endianSize, sizeof(int));
	}
	else
	{
		// no encryption occures, stenograph (fsize indicator, fcontents, fextension)
		outl = inl;
		out = in;

	}


	void * lsbfunction;

	if (params.s == LSB1)
	{
		lsbfunction = lsb1;
	}
	else if (params.s == LSB4)
	{
		lsbfunction = lsb4;
	}
	else
	{
		lsbfunction = lsbe;// TODO cuando este hecha lsbe, agregarla y validacon de lo qretorna hideFile
	}

	hideFile(out, outl, carrier, lsbfunction);
	printf("File %s successfuly encoded into wav file %s\n", params.in, params.p);
	free(out);
	if (in != out) {
		free(in);
	}
	freeStegoFile(sfile);
	return 1;
}

int
CipherData(const EVP_CIPHER * cipher, Params params, unsigned char * in, int inl, unsigned char * out)
{
	int outl, templ;
	unsigned char key[KEY] = {0};
	unsigned char iv[IV] = {0};
	EVP_CIPHER_CTX ctx;

	EVP_BytesToKey(cipher, EVP_md5(),NULL, (unsigned char *)params.pass, strlen(params.pass), 1, (unsigned char *)key, (unsigned char *)iv);
	EVP_CIPHER_CTX_init(&ctx);
	EVP_CipherInit_ex(&ctx, cipher, NULL, (unsigned char *)key, (unsigned char *)iv, params.sop);
	EVP_CipherUpdate(&ctx, out, &outl, in, inl);
	EVP_CipherFinal_ex(&ctx, out + outl, &templ);
	EVP_CIPHER_CTX_cleanup(&ctx);
	return outl + templ;
}
